﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using OverStore.Runtime.ObjectRepository.Attacher;
using OverStore.Configuration.ObjectRepository.Attacher;
using OverStore.Runtime.PersistenceServiceProvider;

namespace OverStore.Configuration.Builder.Fluent
{
    /// <summary>
    /// Contains set of nested classes helps to configure object attacher.
    /// </summary>
    /// <typeparam name="TInstance">The type of the instance.</typeparam>
    /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
    /// <typeparam name="TNext">The type of the next.</typeparam>
    public static class AttacherBuilder<TInstance, TIdentifier, TNext>
        where TInstance : class
    {
        /// <summary>
        /// Data are required for creating object attacher.
        /// </summary>
        public class Parameters
        {
            /// <summary>
            /// Gets or sets the delegate that provides identifier.
            /// </summary>
            public Func<TInstance, TIdentifier> ProvideIdentifier { get; set; }

            /// <summary>
            /// Gets or sets the delegate determines whether instance data changed.
            /// </summary>
            public Func<TInstance, bool> AreInstanceDataChanged { get; set; }

            /// <summary>
            /// Gets or sets the delegate determines whether session must generate new identifier for instance being attached.
            /// </summary>
            public Func<TInstance, bool> GenerateNewIdentifier { get; set; }

            /// <summary>
            /// Gets or sets the delegate determines whether instance is new.
            /// </summary>
            public Func<TInstance, IPersistenceServiceProvider, bool> IsInstanceNew { get; set; }

            /// <summary>
            /// Creates the attacher.
            /// </summary>
            public IComponentConfiguration<IAttacher<TInstance, TIdentifier>> CreateAttacher()
            {
                return new DelegateAttacherConfiguration<TInstance, TIdentifier>
                   {
                       ProvideAttachInfo = (instance, provider, attachInfo) =>
                       {
                           attachInfo.Identifier = ProvideIdentifier(instance);
                           attachInfo.IsInstanceNew = IsInstanceNew(instance, provider);
                           attachInfo.GenerateNewIdentifier = GenerateNewIdentifier(instance);
                           attachInfo.AreInstanceDataChanged = AreInstanceDataChanged(instance);
                       }
                   };
            }

            /// <summary>
            /// Gets or sets the on completed.
            /// </summary>
            public Func<IComponentConfiguration<IAttacher<TInstance, TIdentifier>>, TNext> OnCompleted { get; set; }
        }

        /// <summary>
        /// 
        /// </summary>
        public class IdentifierProvider
        {
            private readonly Parameters _Parameters;

            /// <summary>
            /// Initializes a new instance of the <see cref="AttacherBuilder&lt;TInstance, TIdentifier, TNext&gt;.IdentifierProvider"/> class.
            /// </summary>
            /// <param name="parameters">The parameters.</param>
            public IdentifierProvider(Parameters parameters)
            {
                Debug.Assert(parameters != null, "parameters != null");
                _Parameters = parameters;
            }

            /// <summary>
            /// Sets delegate that extracts the identifier.
            /// </summary>
            public GenerateNewIdentifier ExtractIdentifier(Func<TInstance, TIdentifier> extractIdentifierFunc)
            {
                if (Object.ReferenceEquals(extractIdentifierFunc, null))
                    throw new ArgumentNullException("extractIdentifierFunc");
                _Parameters.ProvideIdentifier = extractIdentifierFunc;
                return new GenerateNewIdentifier(_Parameters);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public class GenerateNewIdentifier
        {
            /// <summary>
            /// 
            /// </summary>
            private readonly Parameters _Parameters;

            /// <summary>
            /// Initializes a new instance of the <see cref="AttacherBuilder&lt;TInstance, TIdentifier, TNext&gt;.GenerateNewIdentifier"/> class.
            /// </summary>
            /// <param name="parameters">The parameters.</param>
            public GenerateNewIdentifier(Parameters parameters)
            {
                Debug.Assert(parameters != null, "parameters != null");
                _Parameters = parameters;
            }

            /// <summary>
            /// Provide delegate determines whether session must generate new identifier for instance being attached.
            /// </summary>
            public IsInstanceNew GenerateNewIdentifierIf(Func<TInstance, bool> condition)
            {
                if (Object.ReferenceEquals(condition, null))
                    throw new ArgumentNullException("condition");
                _Parameters.GenerateNewIdentifier = condition;
                return new IsInstanceNew(_Parameters);
            }

            /// <summary>
            /// Nevers the generate new identifier.
            /// </summary>
            public IsInstanceNew NeverGenerateNewIdentifier()
            {
                return GenerateNewIdentifierIf(i => false);
            }

            /// <summary>
            /// Always generates new identifier.
            /// </summary>
            public IsInstanceNew AlwaysGenerateNewIdentifier()
            {
                return GenerateNewIdentifierIf(i => true);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public class IsInstanceNew
        {
            private readonly Parameters _Parameters;

            /// <summary>
            /// Initializes a new instance of the <see cref="AttacherBuilder&lt;TInstance, TIdentifier, TNext&gt;.IsInstanceNew"/> class.
            /// </summary>
            /// <param name="parameters">The parameters.</param>
            public IsInstanceNew(Parameters parameters)
            {
                if (Object.ReferenceEquals(parameters, null))
                    throw new ArgumentNullException("parameters");
                _Parameters = parameters;
            }

            /// <summary>
            /// Sets delegate determines whether instance is new.
            /// </summary>
            public AreInstanceDataChanged InstanceIsNewIf(Func<TInstance, bool> condition)
            {
                if (Object.ReferenceEquals(condition, null))
                    throw new ArgumentNullException("condition");
                _Parameters.IsInstanceNew = (i, p) => condition(i);
                return new AreInstanceDataChanged(_Parameters);
            }


            /// <summary>
            /// Sets delegate determines whether instance is new.
            /// </summary>
            public AreInstanceDataChanged InstanceIsNewIf(Func<TInstance, IPersistenceServiceProvider, bool> condition)
            {
                if (Object.ReferenceEquals(condition, null))
                    throw new ArgumentNullException("condition");
                _Parameters.IsInstanceNew = condition;
                return new AreInstanceDataChanged(_Parameters);
            }

            /// <summary>
            /// If instance identifier is default value of <typeparamref name="TIdentifier"/> then treat instance being attached as new.
            /// </summary>
            public AreInstanceDataChanged NewWhenDefaultIdentifier()
            {
                return InstanceIsNewIf(i => EqualityComparer<TIdentifier>.Default.Equals(_Parameters.ProvideIdentifier(i), default(TIdentifier)));
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public class AreInstanceDataChanged
        {
            private readonly Parameters _Parameters;

            /// <summary>
            /// Initializes a new instance of the <see cref="AttacherBuilder&lt;TInstance, TIdentifier, TNext&gt;.AreInstanceDataChanged"/> class.
            /// </summary>
            /// <param name="parameters">The parameters.</param>
            public AreInstanceDataChanged(Parameters parameters)
            {
                _Parameters = parameters;
            }

            /// <summary>
            /// Sets delegate determines whether instance data are changed.
            /// </summary>
            public EndAttacher DataChangedIf(Func<TInstance, bool> condition)
            {
                if (Object.ReferenceEquals(condition, null))
                    throw new ArgumentNullException("condition");

                _Parameters.AreInstanceDataChanged = condition;
                return new EndAttacher(_Parameters);
            }

            /// <summary>
            /// Treat instance data as always changed.
            /// </summary>
            public EndAttacher ForceDataChanged()
            {
                return DataChangedIf(i => true);
            }
        }

        /// <summary>
        /// Ends attacher configuration.
        /// </summary>
        public class EndAttacher
        {
            private readonly Parameters _Parameters;

            /// <summary>
            /// Initializes a new instance of the <see cref="AttacherBuilder&lt;TInstance, TIdentifier, TNext&gt;.EndAttacher"/> class.
            /// </summary>
            /// <param name="parameters">The parameters.</param>
            public EndAttacher(Parameters parameters)
            {
                Debug.Assert(parameters != null, "parameters != null");
                _Parameters = parameters;
            }

            /// <summary>
            /// Ends the attacher.
            /// </summary>
            public TNext End()
            {
                return _Parameters.OnCompleted(_Parameters.CreateAttacher());
            }
        }
    }
}
